package robocup.component.math;

import robocup.component.SConf;
import robocup.component.geometry.Vektor;
import robocup.component.worldobjects.Player;

/**
 * Robocup math helper methods.
 */
public final class RCMath {

  /**
   * calculates a geometric row. Used for movement calculations.
   * 
   * @param q -
   *          value (decay)
   * @param n -
   *          time (cycles)
   * @return (1-q^n)/(1-q)
   */
  public final static double calculateRow(final double q,
      final double n) {

    if (q == 1) {
      return n;
    }

    return (1 - Math.pow(q, n)) / (1 - q);
  }

  /**
   * returns the given angle normalized between 0..360
   * 
   * @param angle
   *          in degree
   * @return the given angle normalized between 0..360
   */
  public final static double getPositiveNormalizedAngle(final double angle) {

    final double a = Vektor.normalize(angle);
    if (a < 0) {
      return a + 360.0;
    }
    else {
      return a;
    }
  }

  /**
   * the cycles a ball needs to reach a given distance.
   * 
   * @param initialBallSpeed -
   *          the initial ball's speed
   * @param distance -
   *          the distance for the ball to travel
   * @return Integer.MAX_VALUE if not possible, the needed cycles otherwise
   */
  public final static int getCyclesForBallReachingDistance(final double initialBallSpeed,
      final double distance) {

    if (distance == 0) {
      return 0;
    }

    final double term1 = 1.0 - (distance / initialBallSpeed * (1.0 - SConf
        .getInstance().BALL_DECAY));
    if (term1 <= 0) {
      return Integer.MAX_VALUE;
    }
    else {
      return 1 + (int) (Math.log(term1) / Math
          .log(SConf.getInstance().BALL_DECAY));
    }
  }

  /**
   * This method calculates the gaussian value at a given point for given
   * variance and middle!
   * 
   * @param val -
   *          the value for which the gauss-value will be calculated
   * @param med -
   *          the middle
   * @param var -
   *          the variance
   * @return - calculation of the gauss-function!
   */
  public final static double gauss(final double val,
      final double med,
      final double var) {

    final double gauss = (1.0 / (Math.sqrt(2 * Math.PI) * var)) * Math.exp(-(Math
        .pow(val - med, 2) / 2 * Math.pow(var, 2)));
    return gauss;
  }

  /**
   * tells if an angle is between to other angles (all angles in degree).
   * 
   * @param angle -
   *          the angle
   * @param leftAngle -
   *          the left limit [-180;180]
   * @param rightAngle -
   *          the right limit [-180;180]
   * @return true if the angle is between the limitint angles
   */
  public final static boolean isBetween(double angle,
      final double leftAngle,
      double rightAngle) {

    if (leftAngle < rightAngle) {
      return angle >= leftAngle && angle <= rightAngle;
    }
    else {
      rightAngle += 360;
      angle += angle < 0 ? 360 : 0;
      return angle >= leftAngle && angle <= rightAngle;
    }
  }

  /**
   * tells if an angle is between to positions.
   * 
   * @param angle -
   *          the angle
   * @param basePos -
   *          the base position
   * @param leftPos -
   *          the left limiting position
   * @param rightPos -
   *          the right limiting
   * @return true if the angle is in the triangle of the three vektors seen from
   *         the base position.
   */
  public final static boolean isBetween(final double angle,
      final Vektor basePos,
      final Vektor leftPos,
      final Vektor rightPos) {

    return RCMath.isBetween(angle, basePos.getAngleTo(leftPos), basePos
        .getAngleTo(rightPos));
  }

  /**
   * This method rounds a number to the number of decimal places given in
   * <code>PARAMETER_PRECISION</code>.
   * 
   * @param number
   *          The number to round.
   * @return The rounded number.
   */
  public final static double round(final double number,
      final int precision) {

    final double x = Math.pow(10.0, precision);
    return Math.rint(number * x) / x;
  }

  /**
   * rounds a double value to 2 digits.
   * 
   * @param d -
   *          double value
   * @return rounded double value
   */
  public final static double round2(final double d) {

    return RCMath.round(d, 2);
  }
  
  /**
   * 
   * @param player
   * @param target
   * @param deviation
   * @return
   */
  public final static boolean isHeadingTowards(final Player player,
		  final Vektor target, final double deviation) {
	  return (RCMath.distFromHeadingDir(player, target) < deviation);
  }
  
  /**
   * 
   * @param player
   * @param target
   * @return
   */
  public final static double distFromHeadingDir(final Player player,
		  final Vektor target) {
	  
	  final double angleToTarget = player.getAngleRel(target,
              Player.RELATIVE.TO_BODY);
	  final double distance = player.getDistance(target);
	  
	  return (Math.abs(Math.sin(Math.toRadians(angleToTarget))) * distance);
  }

  /**
   * Calculates the percentage value for the given parameters,
   * if the value (curr) lies between min and max.
   * 
   * @param min
   * @param max
   * @param value
   * @return
   */
  public final static double normalize(double min, double max, double value) {
	  return (value - min) * 100 / (max - min);
  }
  
}
